import time
import logzero
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QColor, QPalette
from PyQt5.QtWidgets import (
    QCheckBox,
    QComboBox,
    QGridLayout,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QSpinBox,
    QWidget,
)

from helpers.mengy_bytes_helper import bytes2Int, getChineseStringLength
import mengy_spac800
import mengy_spac800_bt
from qt_custom_widget.qt_helper import addTooltipInSpinBox
from qt_custom_widget.qt_ScrollMessageBox import ScrollMessageBox

logger = logzero.setup_logger("tabs.3.dido")
DI_FUNC_TIP_COLORS = ("#ff0000", "#951555", "#800000", "#d22780", "#800080", "#c85108")


class Communicate(QObject):

    doSignal = pyqtSignal(int)


class DOSPinBox(QSpinBox):
    def __init__(self, do_index=0):
        super().__init__()
        self.c = Communicate()
        self.do_index = do_index
        self.setSuffix("mS")

    def focusInEvent(self, event):
        super().focusInEvent(event)
        self.c.doSignal.emit(self.do_index)


class Tab3_DI_DO(QWidget):
    def __init__(self, serRecvDir, sendQueuePack, logger=logger):
        super().__init__()
        self.initUI()
        self.refreshTime = time.time()
        self.serRecvDir = serRecvDir
        self.sendQueuePack = sendQueuePack
        self.logger = logger
        self.logger.info("this is tab3 di do para logger")
        self.setObjectName("tab3")
        layout = QGridLayout(self)
        layout.addWidget(self.diParaGroupBox, 0, 0, 1, 3)  # 开入参数
        layout.addWidget(self.doParaGroupBox, 0, 3, 1, 6)  # 开出参数

    def initUI(self):
        """初始化显示构件"""
        self.createDIParaGroupBox()
        self.createDOParaGroupBox()

    def createDIParaGroupBox(self):
        self.diParaOverRangeFlag = False
        self.diParaGroupBox = QGroupBox("开入参数")
        layout = QGridLayout()

        self.diParaTimes = []
        for i in range(mengy_spac800.DI_NUM):
            le = QSpinBox()
            le.setSuffix("mS")
            addTooltipInSpinBox(le, (0, 9999))
            layout.addWidget(QLabel("开入{:>2d} 去抖时间".format(i + 1)), i, 0)
            layout.addWidget(le, i, 1)
            self.diParaTimes.append(le)

        self.diParaFuncs = []
        for i in range(mengy_spac800.DI_NUM):
            le = QComboBox()
            le.addItems(mengy_spac800.DI_FUNC_TYPE)
            layout.addWidget(QLabel("功能"), i, 2)
            layout.addWidget(le, i, 3)
            self.diParaFuncs.append(le)
            self.diParaFuncs[i].currentIndexChanged.connect(self.dealDiFuncName)

        self.diPara_ReadButton = QPushButton("读取")
        self.diPara_WrtieButton = QPushButton("写入")
        self.diPara_ReadButton.clicked.connect(self.dealDiButtonClicked)
        self.diPara_WrtieButton.clicked.connect(self.dealDiButtonClicked)

        layout.addWidget(self.diPara_ReadButton, 12, 0)
        layout.addWidget(self.diPara_WrtieButton, 12, 1)
        self.diParaGroupBox.setLayout(layout)

    def dealDiFuncName(self):
        data = [w.currentIndex() for w in self.diParaFuncs]
        sdata = set(data)
        repetedType = []
        for c in sdata:
            if c != 0 and data.count(c) > 1:
                repetedType.append(c)
        if len(repetedType) > 0:
            self.diPara_WrtieButton.setDisabled(True)
            self.diPara_WrtieButton.setText("功能重复禁止写入")
        else:
            self.diPara_WrtieButton.setDisabled(False)
            self.diPara_WrtieButton.setText("写入")
        # self.logger.info(f'di func repetedType {repetedType}')
        for i, d in enumerate(data):
            pal = self.diParaFuncs[i].palette()
            if d not in repetedType:
                pal.setColor(QPalette.Text, QColor(0, 0, 0))
            else:
                pal.setColor(QPalette.Text, QColor(DI_FUNC_TIP_COLORS[repetedType.index(d)]))
            self.diParaFuncs[i].setPalette(pal)

    def diParaGroupBoxRead(self):
        if any((key not in self.serRecvDir.keys() for key in range(0x0300, 0x030B + 1))):
            return
        if any((key not in self.serRecvDir.keys() for key in range(0x030D, 0x0318 + 1))):
            return
        overRangeList = []
        for i in range(mengy_spac800.DI_NUM):
            data = bytes2Int(self.serRecvDir.get(0x0300 + i))
            self.diParaTimes[i].setValue(data)
            maximum = self.diParaTimes[i].maximum()
            minimum = self.diParaTimes[i].minimum()
            if data > maximum or data < minimum:
                self.diParaTimes[i].setStyleSheet("color:#ff0000;")
                overRangeList.append("开入 {:2d} 去抖时间定值 读取值 {:^5d} 范围 {} ~ {}".format(i + 1, data, minimum, maximum))
            else:
                self.diParaTimes[i].setStyleSheet("")
            data = bytes2Int(self.serRecvDir.get(0x030D + i))
            self.diParaFuncs[i].setCurrentIndex(data)
            if data > self.diParaFuncs[i].count() - 1:
                self.diParaFuncs[i].setStyleSheet("color:#ff0000;")
                overRangeList.append("开入 {:2d} 功能选择定值 读取值越限 {}".format(i + 1, data))
            else:
                self.diParaFuncs[i].setStyleSheet("color:#000000;")
        if len(overRangeList) > 0 and not self.diParaOverRangeFlag:
            self.diParaOverRangeFlag = True
            messageBox = ScrollMessageBox(msgList=overRangeList, width=330, parent=self)
            messageBox.setWindowTitle("开入参数超范围")
            messageBox.exec_()
            self.diParaOverRangeFlag = False

    def dealDiButtonClicked(self):
        sender = self.sender()
        self.logger.info(sender is self.diPara_ReadButton)
        if sender is self.diPara_ReadButton:  # 读取开入参数
            self.diParaGroupBoxRead()
        elif sender is self.diPara_WrtieButton:  # 设置开入参数
            for w in self.diParaTimes + self.diParaFuncs:
                w.setStyleSheet("color:#ff0000;")
            data = [i.value() for i in self.diParaTimes]
            self.sendQueuePack(funCode=0x10, start=0x0300, data=data)
            data = [i.currentIndex() for i in self.diParaFuncs]
            self.sendQueuePack(funCode=0x10, start=0x030D, data=data)
            QTimer.singleShot(1500, self.diParaGroupBoxRead)

    def createDOParaGroupBox(self):
        self.doParaOverRangeFlag = False
        self.doParaGroupBox = QGroupBox("开出参数")
        layout = QGridLayout()

        self.doParaTimeGroupBox = QGroupBox("输出时间")
        doTimeLayout = QGridLayout()
        self.doParaTimes = []
        for i in range(8):
            subLayout = QHBoxLayout()
            le = DOSPinBox(i)
            le.setSuffix("mS")
            addTooltipInSpinBox(le, (0, 60000))
            le.c.doSignal.connect(self.doDealTimeConnectPrt)
            if i == 8:
                subLayout.addWidget(QLabel("保护灯 输出时间"))
            elif i == 9:
                subLayout.addWidget(QLabel("告警灯 输出时间"))
            else:
                subLayout.addWidget(QLabel("开出{:>2d} 输出时间".format(i + 1)))
            subLayout.addWidget(le)
            subLayout.addStretch(1)
            doTimeLayout.addLayout(subLayout, i, 0)
            self.doParaTimes.append(le)
        self.doParaTimeGroupBox.setLayout(doTimeLayout)

        self.doParaPrtGroupBox = QGroupBox("保护选择")
        doPrtLayout = QGridLayout()
        self.doParaPrtFuncs = []
        self.doParaPrtIndex = QComboBox()
        self.doParaPrtIndex.currentIndexChanged.connect(self.dealDoParaPrtIndexChange)
        self.doparaPrtData = []
        self.ptrParaDoData = [0 for i in range(len(mengy_spac800.DO_PRT_TYPE_NAME))]
        self.doParaPrtIndex.addItems(("开出{:d}".format(i + 1) for i in range(mengy_spac800.DO_NUM_WITHOUT_LED)))
        self.doParaPrtIndex.addItems(("保护灯", "告警灯"))
        doPrtLayout.addWidget(QLabel("开出选择"), 0, 0, 1, 1)
        doPrtLayout.addWidget(self.doParaPrtIndex, 0, 1, 1, 2)
        for i, n in enumerate(mengy_spac800.DO_PRT_TYPE_NAME):
            lc = QCheckBox(n)
            lc.stateChanged.connect(self.doParaUpdatePrtData)
            self.doParaPrtFuncs.append(lc)
            doPrtLayout.addWidget(lc, i % 10 + 1, i // 10)
        self.doParaPrtGroupBox.setLayout(doPrtLayout)

        self.doPara_ReadButton = QPushButton("读取")
        self.doPara_WrtieButton = QPushButton("写入")

        self.doPara_ReadButton.clicked.connect(self.dealDoButtonClicked)
        self.doPara_WrtieButton.clicked.connect(self.dealDoButtonClicked)

        layout.addWidget(self.doParaTimeGroupBox, 0, 0, 10, 2)
        layout.addWidget(self.doParaPrtGroupBox, 0, 2, 10, 4)
        layout.addWidget(self.doPara_ReadButton, 10, 1, 1, 1)
        layout.addWidget(self.doPara_WrtieButton, 10, 2, 1, 1)

        self.doParaGroupBox.setLayout(layout)

    @pyqtSlot(int)
    def doDealTimeConnectPrt(self, doIndex):
        """选定开出时间时 切换显示对应的保护选择"""
        # self.logger.info(doIndex)
        self.doParaPrtIndex.setCurrentIndex(doIndex)
        self.dealDoParaPrtIndexChange()

    def doParaGetPrtData(self):
        """获取开出参数中 保护对应的开出配置"""
        if any((key not in self.serRecvDir.keys() for key in range(0x040A, 0x0426 + 1))):
            return
        for i in range(len(mengy_spac800.DO_PRT_TYPE_NAME)):
            data = bytes2Int(self.serRecvDir.get(0x040A + i))
            self.ptrParaDoData[i] = data
            # self.logger.info(f"{i+1:>2d} --> 0b{data:010b}")

    def doParaConvertPrtData(self):
        """将保护对应的开出配置 转换成 开出对应的保护配置"""
        for i in range(10):
            data = 0
            for idx, p in enumerate(self.ptrParaDoData):
                if p & (1 << i):
                    data += 1 << idx
            if len(self.doparaPrtData) < 10:
                self.doparaPrtData.append(data)
            else:
                self.doparaPrtData[i] = data
            # self.logger.info(f"{i+1:>2d} <-- 0b{data:029b}")

    def doParaNConvertPrtData(self):
        """逆转 doParaConvertPrtData 操作"""
        for i in range(len(self.ptrParaDoData)):
            data = 0
            for idx, p in enumerate(self.doparaPrtData):
                if p & (1 << i):
                    data += 1 << idx
            self.ptrParaDoData[i] = data
            # self.logger.info(f"{i+1:>2d} --> 0b{data:010b}")

    def doParaShowPrtData(self):
        """显示 开出对应的保护配置 """
        showData = self.doparaPrtData[self.doParaPrtIndex.currentIndex()]
        for i, c in enumerate(self.doParaPrtFuncs):
            if showData & (1 << i):
                c.setChecked(True)
                c.setStyleSheet("color: red")
            else:
                c.setChecked(False)
                c.setStyleSheet("color: black")

    def doParaUpdatePrtData(self):
        """当勾选项发生变化时 更新 开出对应的保护配置"""
        data = 0
        for i, c in enumerate(self.doParaPrtFuncs):
            if c.checkState():
                data += 1 << i
        # self.logger.info(f'data changed 0b{self.doparaPrtData[self.doParaPrtIndex.currentIndex()]:029b} --> 0b{data:>029b}')
        self.doparaPrtData[self.doParaPrtIndex.currentIndex()] = data

    def doParaGroupBoxRead(self):
        """读取开出参数 按钮"""
        if any((key not in self.serRecvDir.keys() for key in range(0x0400, 0x0409 + 1))):
            return
        overRangeList = []
        for i, w in enumerate(self.doParaTimes):
            data = bytes2Int(self.serRecvDir.get(0x0400 + i))
            w.setValue(data)
            maximum = w.maximum()
            minimum = w.minimum()
            if data > maximum or data < minimum:
                w.setStyleSheet("color:#ff0000;")
                if i < 8:
                    msgText = "开出 {:d} 输出时间定值 读取值 {:^5d} 范围 {} ~ {}".format(i + 1, data, minimum, maximum)
                else:
                    msgText = "{} 输出时间定值 读取值 {:^5d} 范围 {} ~ {}".format(("保护灯", "告警灯")[i - 8], data, minimum, maximum)
                overRangeList.append(msgText)
            else:
                w.setStyleSheet("")
        self.doParaGetPrtData()
        self.doParaConvertPrtData()
        self.doParaShowPrtData()
        for i, d in enumerate(self.ptrParaDoData):
            if d > 0x3FF:  # 超过10开出位
                prtName = "{0:^{1}}".format(mengy_spac800.DO_PRT_TYPE_NAME[i], 14 - getChineseStringLength(mengy_spac800.DO_PRT_TYPE_NAME[i]))
                overRangeList.append("{} 出口选择超范围 读取值 0X{:04X} 范围 0 ~ 0x3FF".format(prtName, d))
        if len(overRangeList) > 0 and not self.doParaOverRangeFlag:
            self.doParaOverRangeFlag = True
            messageBox = ScrollMessageBox(msgList=overRangeList, width=400, parent=self)
            messageBox.setWindowTitle("出口参数超范围")
            messageBox.exec_()
            self.doParaOverRangeFlag = False

    def dealDoWriteButton(self):
        """写入开出参数 按钮"""
        for w in self.doParaTimes:
            w.setStyleSheet("color:#ff0000;")
        data = [i.value() for i in self.doParaTimes]
        self.sendQueuePack(funCode=0x10, start=0x0400, data=data)
        self.doParaNConvertPrtData()
        data = self.ptrParaDoData
        self.sendQueuePack(funCode=0x10, start=0x040A, data=data)
        QTimer.singleShot(1500, self.doParaGroupBoxRead)

    def dealDoButtonClicked(self):
        """按钮插槽"""
        sender = self.sender()
        self.logger.info(sender is self.doPara_ReadButton)
        if sender is self.doPara_ReadButton:
            self.doParaGroupBoxRead()
        elif sender is self.doPara_WrtieButton:
            self.dealDoWriteButton()

    def dealDoParaPrtIndexChange(self):
        """开出保护显示时 处理开出序号发生变化"""
        if len(self.doparaPrtData) > 0:
            self.doParaGetPrtData()
            self.doParaConvertPrtData()
            self.doParaShowPrtData()

    def updateBySerialRecvBuff(self, changeAddrList):
        """数据缓存发生变化时需要刷新部分"""
        if any(i in range(0x0300, 0x0318 + 1) for i in changeAddrList):
            self.diParaGroupBoxRead()
            self.refreshTime = time.time()
        if any(i in range(0x0400, 0x0409 + 1) for i in changeAddrList):
            self.doParaGroupBoxRead()
            self.refreshTime = time.time()

    def updateByTimer(self, timeout=1):
        if time.time() - self.refreshTime > timeout:
            self.diParaGroupBoxRead()
            self.doParaGroupBoxRead()
            self.refreshTime = time.time()

    def updateUnconditional(self):
        """无条件刷新界面显示部分"""
        pass

    def updateWhole(self):
        """更新全体显示构件"""
        self.diParaGroupBoxRead()
        self.doParaGroupBoxRead()
        self.refreshTime = time.time()


class Tab3_DI_DO_BT(Tab3_DI_DO):
    def createDIParaGroupBox(self):
        self.diParaOverRangeFlag = False
        self.diParaGroupBox = QGroupBox("开入参数")
        layout = QGridLayout()

        self.diParaTimes = []
        for i in range(mengy_spac800_bt.DI_NUM):
            le = QSpinBox()
            le.setSuffix("mS")
            addTooltipInSpinBox(le, (0, 9999))
            layout.addWidget(QLabel("开入{:>2d} 去抖时间".format(i + 1)), i, 0)
            layout.addWidget(le, i, 1)
            self.diParaTimes.append(le)

        self.diParaFuncs = []
        for i in range(mengy_spac800_bt.DI_NUM):
            le = QComboBox()
            le.addItems(mengy_spac800_bt.DI_FUNC_TYPE)
            layout.addWidget(QLabel("功能"), i, 2)
            layout.addWidget(le, i, 3)
            self.diParaFuncs.append(le)
            self.diParaFuncs[i].currentIndexChanged.connect(self.dealDiFuncName)

        self.diPara_ReadButton = QPushButton("读取")
        self.diPara_WrtieButton = QPushButton("写入")
        self.diPara_ReadButton.clicked.connect(self.dealDiButtonClicked)
        self.diPara_WrtieButton.clicked.connect(self.dealDiButtonClicked)

        layout.addWidget(self.diPara_ReadButton, 12, 0)
        layout.addWidget(self.diPara_WrtieButton, 12, 1)
        self.diParaGroupBox.setLayout(layout)

    def createDOParaGroupBox(self):
        self.doParaOverRangeFlag = False
        self.doParaGroupBox = QGroupBox("开出参数")
        layout = QGridLayout()

        self.doParaTimeGroupBox = QGroupBox("输出时间")
        doTimeLayout = QGridLayout()
        self.doParaTimes = []
        for i in range(8):
            subLayout = QHBoxLayout()
            le = DOSPinBox(i)
            le.setSuffix("mS")
            addTooltipInSpinBox(le, (0, 60000))
            le.c.doSignal.connect(self.doDealTimeConnectPrt)
            if i == 8:
                subLayout.addWidget(QLabel("保护灯 输出时间"))
            elif i == 9:
                subLayout.addWidget(QLabel("告警灯 输出时间"))
            else:
                subLayout.addWidget(QLabel("开出{:>2d} 输出时间".format(i + 1)))
            subLayout.addWidget(le)
            subLayout.addStretch(1)
            doTimeLayout.addLayout(subLayout, i, 0)
            self.doParaTimes.append(le)
        self.doParaTimeGroupBox.setLayout(doTimeLayout)

        self.doParaPrtGroupBox = QGroupBox("保护选择")
        doPrtLayout = QGridLayout()
        self.doParaPrtFuncs = []
        self.doParaPrtIndex = QComboBox()
        self.doParaPrtIndex.currentIndexChanged.connect(self.dealDoParaPrtIndexChange)
        self.doparaPrtData = []
        self.ptrParaDoData = [0 for i in range(len(mengy_spac800_bt.DO_PRT_TYPE_NAME))]
        self.doParaPrtIndex.addItems(("开出{:d}".format(i + 1) for i in range(mengy_spac800_bt.DO_NUM_WITHOUT_LED)))
        self.doParaPrtIndex.addItems(("保护灯", "告警灯"))
        doPrtLayout.addWidget(QLabel("开出选择"), 0, 0, 1, 1)
        doPrtLayout.addWidget(self.doParaPrtIndex, 0, 1, 1, 2)
        for i, n in enumerate(mengy_spac800_bt.DO_PRT_TYPE_NAME):
            lc = QCheckBox(n)
            lc.stateChanged.connect(self.doParaUpdatePrtData)
            self.doParaPrtFuncs.append(lc)
            doPrtLayout.addWidget(lc, i % 10 + 1, i // 10)
        self.doParaPrtGroupBox.setLayout(doPrtLayout)

        self.doPara_ReadButton = QPushButton("读取")
        self.doPara_WrtieButton = QPushButton("写入")

        self.doPara_ReadButton.clicked.connect(self.dealDoButtonClicked)
        self.doPara_WrtieButton.clicked.connect(self.dealDoButtonClicked)

        layout.addWidget(self.doParaTimeGroupBox, 0, 0, 10, 2)
        layout.addWidget(self.doParaPrtGroupBox, 0, 2, 10, 4)
        layout.addWidget(self.doPara_ReadButton, 10, 1, 1, 1)
        layout.addWidget(self.doPara_WrtieButton, 10, 2, 1, 1)

        self.doParaGroupBox.setLayout(layout)

    def doParaGetPrtData(self):
        """获取开出参数中 保护对应的开出配置"""
        if any((key not in self.serRecvDir.keys() for key in range(0x040A, 0x0426 + 1))):
            return
        for i in range(len(mengy_spac800_bt.DO_PRT_TYPE_NAME)):
            data = bytes2Int(self.serRecvDir.get(0x040A + i))
            self.ptrParaDoData[i] = data
            # self.logger.info(f"{i+1:>2d} --> 0b{data:010b}")

    def doParaGroupBoxRead(self):
        """读取开出参数 按钮"""
        if any((key not in self.serRecvDir.keys() for key in range(0x0400, 0x0409 + 1))):
            return
        overRangeList = []
        for i, w in enumerate(self.doParaTimes):
            data = bytes2Int(self.serRecvDir.get(0x0400 + i))
            w.setValue(data)
            maximum = w.maximum()
            minimum = w.minimum()
            if data > maximum or data < minimum:
                w.setStyleSheet("color:#ff0000;")
                if i < 8:
                    msgText = "开出 {:d} 输出时间定值 读取值 {:^5d} 范围 {} ~ {}".format(i + 1, data, minimum, maximum)
                else:
                    msgText = "{} 输出时间定值 读取值 {:^5d} 范围 {} ~ {}".format(("保护灯", "告警灯")[i - 8], data, minimum, maximum)
                overRangeList.append(msgText)
            else:
                w.setStyleSheet("")
        self.doParaGetPrtData()
        self.doParaConvertPrtData()
        self.doParaShowPrtData()
        for i, d in enumerate(self.ptrParaDoData):
            if d > 0x3FF:  # 超过10开出位
                prtName = "{0:^{1}}".format(mengy_spac800_bt.DO_PRT_TYPE_NAME[i], 14 - getChineseStringLength(mengy_spac800_bt.DO_PRT_TYPE_NAME[i]))
                overRangeList.append("{} 出口选择超范围 读取值 0X{:04X} 范围 0 ~ 0x3FF".format(prtName, d))
        if len(overRangeList) > 0 and not self.doParaOverRangeFlag:
            self.doParaOverRangeFlag = True
            messageBox = ScrollMessageBox(msgList=overRangeList, width=400, parent=self)
            messageBox.setWindowTitle("出口参数超范围")
            messageBox.exec_()
            self.doParaOverRangeFlag = False
